﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Globalization;


namespace DmPlayer
{
    /// <summary>
    /// DmCanvas.xaml 的交互逻辑
    /// </summary>
    public partial class DmCanvas : Window
    {
        public DmTrajectoryManager dmTrajectoryManager;

        static Semaphore drawDmSemaphore = new Semaphore(4, 4);

        public DmCanvas()
        {
            InitializeComponent();

            this.Width = SystemParameters.PrimaryScreenWidth;
            this.Height = SystemParameters.PrimaryScreenHeight;
            this.Top = 0;
            this.Left = 0;

            dmTrajectoryManager = new DmTrajectoryManager(this.Width, this.Height, 10);
        }

        public void launchDm(Dm dm)
        {
            new Thread(() => 
            {
                MemoryStream ms = convertDmToPngStream(dm);
                Dispatcher.BeginInvoke(new Action(
                delegate {
                    //从MemoryStream中读取弹幕图片
                    PngBitmapDecoder decoder = new PngBitmapDecoder(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
                    Image dmImage = new Image();
                    dmImage.Source = decoder.Frames[0];

                    //设置弹幕初始位置
                    double w = dmImage.Source.Width, h = dmImage.Source.Height;

                    uint y = dmTrajectoryManager.occupy(w, h);
                    if (y < this.Height)
                    {
                        dmImage.SetValue(Canvas.LeftProperty, dmCanvas.ActualWidth);
                        dmImage.SetValue(Canvas.TopProperty, (double)y);

                        dmCanvas.Children.Add(dmImage);

                        //设置弹幕特技
                        DoubleAnimation dmFloatAnimation = new DoubleAnimation(dmCanvas.ActualWidth, -w, TimeSpan.FromSeconds(10));
                        dmFloatAnimation.Completed += new EventHandler((object o, EventArgs ea) => { dmCanvas.Children.Remove(dmImage); });
                        dmImage.BeginAnimation(Canvas.LeftProperty, dmFloatAnimation);
                    }

                }));
            }).Start();
        }

        private MemoryStream convertDmToPngStream(Dm dm)
        {
            Color color = Color.FromRgb((byte)(dm.color >> 16), (byte)((dm.color >> 8) & 0xff), (byte)(dm.color & 0xff));
            bool whiteBorder = (color.R * 0.299 + color.G * 0.587 + color.B * 0.114) < 127.5;  //判断弹幕描白边还是黑边

            drawDmSemaphore.WaitOne();

            FormattedText ft = new FormattedText(
                dm.text,
                CultureInfo.GetCultureInfo("zh-cn"),
                FlowDirection.LeftToRight,
                new Typeface(dm.font),
                dm.size,
                Brushes.Black);

            ft.SetFontWeight(FontWeights.Bold);

            DrawingVisual drawingVisual = new DrawingVisual();
            DrawingContext drawingContext = drawingVisual.RenderOpen();
            drawingContext.DrawGeometry(new SolidColorBrush(color), new Pen(whiteBorder ? Brushes.White : Brushes.Black, dm.size * 0.025), ft.BuildGeometry(new Point(0, 0)));
            drawingContext.Close();
            RenderTargetBitmap dmBmp = new RenderTargetBitmap((int)ft.Width, (int)ft.Height, 96, 96, PixelFormats.Default);
            dmBmp.Render(drawingVisual);
            PngBitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(dmBmp));
            MemoryStream ms = new MemoryStream();
            encoder.Save(ms);

            drawDmSemaphore.Release();

            return ms;

        }
    }

    public class DmTrajectoryManager
    {
        private uint[][][] trajectoryArray;
        private uint timespan;
        private uint width, height;
        private DateTime baseTime = new DateTime(1970, 1, 1);
        private uint maxLayer = 5;
        public DmTrajectoryManager(double width, double height, uint dmTimeSpan)
        {
            trajectoryArray = new uint[maxLayer][][];
            uint timestamp = currentTimestamp();
            this.width = (uint)Math.Ceiling(width);
            this.height = (uint)Math.Ceiling(height) + 1; //要+1不然铺不满屏幕，我也不知道为什么
            for (int j = 0; j < maxLayer; j++) { 
                trajectoryArray[j] = new uint[this.height][];
                for (uint i = 0; i < this.height; i++)
                {
                    trajectoryArray[j][i] = new uint[3];
                    trajectoryArray[j][i][0] = timestamp;
                    trajectoryArray[j][i][1] = 0;
                    trajectoryArray[j][i][2] = 1;
                }
            }

            timespan = dmTimeSpan;

        }

        public uint occupy(double dmWidth, double dmHeight)
        {
            Debug.Assert(width > 1.0);
            Debug.Assert(height > 1.0);


            //寻找能放下这条弹幕的弹道
            uint timestamp = currentTimestamp();
            uint dw = (uint)Math.Ceiling(dmWidth), dh = (uint)Math.Ceiling(dmHeight);

            uint result = this.height + 10; //设置一个比屏幕高度大的值表示无效值

            for (int layer = 0; layer < maxLayer; layer++) //
            {
                uint p = 0, h = 0;
                for (uint i = 0; i < this.height;)
                {
                    uint currentTrajectoryTimestamp = trajectoryArray[layer][i][0];
                    uint currentTrajectoryDmWidth = trajectoryArray[layer][i][1];

                    uint last = timestamp - currentTrajectoryTimestamp;

                    bool cond = true;
                    if (trajectoryArray[layer][i][1] == 0)
                    {
                        cond = true;
                    }
                    else
                    {
                        uint total0 = width + currentTrajectoryDmWidth, total1 = width + dw;

                        uint dist = (uint)Math.Ceiling((double)last / (timespan * 1000) * total0);        //当前时刻上一条在这条弹道上的弹幕的位移
                        uint time = (uint)(Math.Ceiling((double)width / total1 * timespan * 1000));       //待发射的弹幕的左边界接触到屏幕左边界所需时间

                        bool collideAtStart = dist < currentTrajectoryDmWidth;                                  //如果上一条弹幕的位移小于它的长度
                                                                                                          //则会在屏幕右边界与待发射的弹幕冲突

                        bool collideAtComplete = time < timespan * 1000 - last && timespan * 1000 > last; //如果上一条弹幕还在屏幕上
                                                                                                          //而且它留在屏幕上的剩余时间
                                                                                                          //比待发射的弹幕的左边界接触到屏幕左边界所需时间长
                                                                                                          //则会在屏幕左边界和待发射的弹幕冲突

                        cond = (!collideAtStart) && (!collideAtComplete);

                    }

                    if (cond)
                    {
                        h++;
                        i++;
                        if (h == dh)
                            break;
                    }
                    else
                    {
                        i += trajectoryArray[layer][i][2]; //跳过上一条弹幕占据的所有弹道
                        p = i;
                        h = 0;
                    }
                }

                if(this.height - p > dh)
                {
                    result = p;


                    timestamp = currentTimestamp();

                    for (uint i = p; i < p + dh && i < height; i++)
                    {
                        trajectoryArray[layer][i][0] = timestamp;
                        trajectoryArray[layer][i][1] = dw;
                        trajectoryArray[layer][i][2] = dh--;
                    }

                    break;
                }
            }
            
            return result;

        }

        private uint currentTimestamp()
        {
            return (uint)Math.Ceiling((DateTime.Now - baseTime).TotalMilliseconds);
        }
    }
}
